#!/usr/bin/python

# Copyright 2010, Fei Yu, feiyu00 [at] gmail [dot] com

# This file is part of Topics-Portal.
#
# Topics-Portal is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Topics-Portal is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PyGrams.  If not, see <http://www.gnu.org/licenses/>.

from django.shortcuts import render_to_response, get_object_or_404
from django.http import Http404, HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.conf import settings
# from ims.sti.models import *
from couchdbkit import *
import re
import math
import random
import copy
import difflib
import simplejson
import urllib
import urllib2
import logging


server = client.Server(settings.COUCHDB_HOST)
db = server[settings.COUCHDB_DATABASE_NAME]

def home(request):
	return render_to_response('home.html', {'title': 'Welcome'})


def bulk_fetch_from_db(keys, db, include_docs=False):
	"""Given a list of keys, the function will return a list of docs corresponding 
	 to the keys."""
	include_docs = 'true' if include_docs else 'false'
	keys = list(set(keys))	# remove duplication
	rows = simplejson.load(urllib2.urlopen(\
			settings.COUCHDB_HOST + db + '/_all_docs' + '?include_docs=' + include_docs,
			simplejson.dumps({"keys": keys})))['rows']
	
	# get valid rows
	result = []
	for r in rows:
		if not 'error' in r:
			del r['doc']['_rev']
			result.append(r['doc'])
	return result
	
def mscNavigation(request): 
	return render_to_response('msc_2010.html')			

def mscTag(request, tag):
	def get_cooccur_tm_data(cooccur_data):
		class Node(dict):
			def __init__(self, id, name, depth=0):
				self['id'] = id
				self['name'] = name
				self['parent'] = None
				self['children'] = []
				self['data'] = {'id': id, 'count':0, '$area': 1, 'grand_total': 0}
				self['depth'] = depth
				self['max_area'] = 10
				
			def to_dict(self):
				node_dict = {}
				for key in self:
					node_dict[key] = self[key]
					if key == 'parent' and self[key]:
						# node_dict['parent'] = self['parent'].to_dict()
						node_dict['parent'] = self['parent']['id']	# for the purpose of tree map
						
					if key == 'children' and self[key]:
						children = []
						for ch in self['children']:
							children.append(ch.to_dict())
						node_dict['children'] = children
				return node_dict

		class Tree(dict):
			def __init__(self):
				self['nodes'] = {}
				self['max_depth'] = 0
				
			def getNode(self, node_id):
				return self['nodes'].get(node_id, None)
				
			def addNode(self, node, parent_id=None):
				if parent_id:
					parent = self.getNode(parent_id)
					if not parent:
						return False
					node['parent'] = parent
					parent['children'].append(node)
				self['nodes'][node['id']] = node
				return True
				
			def get_node_total_count(self, node):
				total = node['data']['count']
				for ch in node['children']:
					total += self.get_node_total_count(ch)
				return total
			
			def set_node_total_count(self, node):
				node['data']['grand_total'] = self.get_node_total_count(node)
			
			def set_all_nodes_total(self):
				depth = self['max_depth']
				while depth >= 0:
					for key in self['nodes']:
						node = self['nodes'][key]
						if node['depth'] != depth:
							continue
						self.set_node_total_count(node)
					depth -= 1
					
			def set_node_area(self, node):
				node['data']['$area'] = min(node['data']['grand_total'], node['max_area'])
				for ch in node['children']:
					self.set_node_area(ch)
					
			def set_all_nodes_area(self):
				depth = 0
				for key in self['nodes']:
					node = self['nodes'][key]
					if node['depth'] != depth:
						continue
					self.set_node_area(node)

			def to_array(self):
				depth = 0
				result = []
				for key in self['nodes']:
					node = self['nodes'][key]
					if node['depth'] != depth:
						continue
					result.append(node.to_dict())
				return result
					
			
			
		def make_tree(cooccur_data_map):
			""" A tag has the following structure: ['aabcc'].
			If 'bcc' == '-++', then the tag is of level 0;
			else if 'cc' == 'xx', then the tag is of level 1;
			the tag is of level 2 otherwise."""
			tags = cooccur_data_map.keys()
			
			tree = Tree()
			for tag in tags:
				name = tag[:2] + '-++' # add all Depth 0 nodes even if they don't exist in the array tags
				if tree.getNode(name):
					continue
				node = Node(name, name, 0)
				if node['id'] in cooccur_data_map:
					node['data'].update(cooccur_data_map[node['id']])
				tree.addNode(node)
				tree['max_depth'] = 0
			
			for tag in tags:
				if tag[2:5] != '-++':
					name = tag[:3] + 'xx' # add all Depth 1 nodes even if they don't exist in the array tags
					if tree.getNode(name):
						continue
					node = Node(name, name, 1)
					if node['id'] in cooccur_data_map:
						node['data'].update(cooccur_data_map[node['id']])
					parent_id = tag[:2] + '-++'
					tree.addNode(node, parent_id)
					tree['max_depth'] = 1
			
			for tag in tags:
				if tag[3:5] != 'xx':
					name = tag
					if tree.getNode(name):
						continue
					node = Node(name, name, 2)
					if node['id'] in cooccur_data_map:
						node['data'].update(cooccur_data_map[node['id']])
					parent_id = tag[:3]+'xx'
					tree.addNode(node, parent_id)
					tree['max_depth'] = 2
			return tree
			
			
			
		# end of functions and classes for make_tree()
		cooccur_data_map = dict([(d['id'], d) for d in cooccur_data])
		tags = cooccur_data_map.keys()
		tree = make_tree(cooccur_data_map)
		
		# update tree data
		tags = tree['nodes'].keys()
		msc_tag_docs = bulk_fetch_from_db(tags, MSC_TAG_DB, include_docs=True)
		for doc in msc_tag_docs:
			node = tree['nodes'].get(doc['_id'], None)
			if node:
				node['data'].update(doc)
		
		tree.set_all_nodes_total()
		tree.set_all_nodes_area()
		result = {'id': 'root', 'name': 'root', 'data': {}, 'children': tree.to_array()}
		return result
		
	# end of make_jit_data
	######################
		
	def ztag_to_mtag(tag):
		"""This function translates a zmath tag in ZMATH DB to an msc tag in MSC DB"""
		return tag.replace('-XX', '-++').replace('XX', 'xx')
		
	def get_author_data(authors):
		def sort(x, y):
			return -cmp(x['count'], y['count']) \
						if cmp(x['count'], y['count']) != 0 else cmp(x['name'], y['name'])
						
		author_counts = {}
		for au in authors:
			if not au in author_counts:
				author_counts[au] = 0
			author_counts[au] += 1

		return sorted([{'name': au, 'count': count} for au, count in author_counts.items()], 
						cmp=sort)
	
	def get_year_data(years):
		def sort(x,y):
			return -cmp(x['count'], y['count']) \
						if cmp(x['count'], y['count']) != 0 else -cmp(x['year'], y['year'])		
		year_counts = {}
		for y in years:
			if not y in year_counts:
				year_counts[y] = 0
			year_counts[y] += 1
		
		return sorted([{'year': year, 'count': count} for year, count in year_counts.items()],
						cmp=sort)	
	
	def get_cooccur_data(cooccur_dict, msc_tag_docs_dict):
		def sort(x, y):
			return -cmp(x['count'], y['count']) \
						if cmp(x['count'], y['count']) != 0 else cmp(x['id'], y['id'])
		result = []
		for tag in cooccur_dict:
			count = len(cooccur_dict[tag])
			mtag = ztag_to_mtag(tag)
			if not mtag in msc_tag_docs_dict:
				continue
			doc = msc_tag_docs_dict[mtag]
			doc['id'] = mtag
			doc['count'] = count
			result.append(doc)
		return sorted(result, cmp=sort)
	
	def get_author_cloud_data(author_data):
		import copy
		author_data = copy.deepcopy(author_data)
		MAX_LEVEL = 10
		increment = 2
		min_level = 0
		result = []
		# create map with counts as keys
		author_data_map_by_count = {}
		for au in author_data:
			if not au['count'] in author_data_map_by_count:
				author_data_map_by_count[au['count']] = []
			author_data_map_by_count[au['count']].append(au)
		
		# normalize tag sizes
		counts = sorted(author_data_map_by_count.keys())
		if len(counts) < 4:
			increment = 6
		elif len(counts) < 5:
			increment = 5;
		elif len(counts) < 6:
			increment = 4
		
		multiplier = 1 if len(counts) < MAX_LEVEL else float(MAX_LEVEL) / len(counts)
		for index, count in enumerate(counts):
			for doc in author_data_map_by_count[count]:
				doc['size'] = int(min_level + increment * index * multiplier)
				result.append(doc)
		
		return sorted(result, cmp=lambda x,y: cmp(x['name'], y['name']) )
	############ end of helper functions	
	
	MSC_TAG_DB = 'msc2010'
	ZMATH_TAG_DB = 'zmath_tag_all'
	
	# get tag_doc from ZMATH_TAG_DB
	rows = bulk_fetch_from_db([tag], ZMATH_TAG_DB, include_docs=True)
	if not rows:
		return render_to_response('noResults.html')
	zmath_root_doc = rows[0]
	tags = [zmath_root_doc['_id']] + zmath_root_doc['cooccur'].keys()
	
	msc_tag_docs = bulk_fetch_from_db([ztag_to_mtag(t) for t in tags], 
											MSC_TAG_DB, include_docs=True)
	msc_tag_docs_dict = dict([(doc['_id'], doc) for doc in msc_tag_docs])
										
	author_data = get_author_data(zmath_root_doc['authors'])
	year_data = get_year_data(zmath_root_doc['years'])									
	cooccur_data = get_cooccur_data(zmath_root_doc['cooccur'], msc_tag_docs_dict)
	
	msc_root_doc = msc_tag_docs_dict.get(ztag_to_mtag(zmath_root_doc['_id']), {})
	tag_text = '; '.join([e['defnText'] for e \
						in msc_root_doc['defns']]) if 'defns' in msc_root_doc else ''									
	context = {'title': '[' + tag + '] ' + tag_text,
				'json_cooccur_data': simplejson.dumps(cooccur_data),
				'json_year_data': simplejson.dumps(year_data),
				'author_cloud_data': get_author_cloud_data(author_data),
				'json_author_data': simplejson.dumps(author_data),
				'json_cooccur_tm_data': simplejson.dumps(get_cooccur_tm_data(cooccur_data)), 
				}
	
	return render_to_response('msc_tag.html', context)



def author_network_graph(request):
	db_host = "http://localhost:5984/"
	db_name = "zmath_author_coauthors"
	
	def search(term, limit=10, is_lucene=False):
		if is_lucene:
			term = term.strip().lower().replace(',', ' ')
			q = {'limit': limit, 'q': term, 'include_docs': 'true', 'stale': 'ok'}
			view = '/_fti/_design/lucene/authors?'
		else:
			q = {'limit': limit, 'startkey': simplejson.dumps(term)}
			view = '/_design/msc/_view/by_author?'
		
		url = db_host + 'zmath_author_coauthors' + view + urllib.urlencode(q)
		response = simplejson.load(urllib.urlopen(url))
		if response['rows']:
			if is_lucene:
				return [{'author': r['doc']['author']} for r in response['rows'] if 'author' in r['doc'] ]
			else:
				return [{'author': r['key']} for r in response['rows']]
		return None
		
	def get_random_author():
		url_head = db_host + db_name + '/_design/msc/_view/random_author'
		url = url_head + '?' + urllib.urlencode({'limit': 1, 'startkey': random.random(), 'include_docs': 'true'})
		response = simplejson.load(urllib.urlopen(url))
		if not response['rows']:
			return get_random_author()
		else:
			return response['rows'][0]['doc']
	
	def get_authors(keys):
		url = db_host + db_name + '/_design/msc/_view/by_author?include_docs=true'
		response = simplejson.load(urllib2.urlopen(url, simplejson.dumps({"keys": keys})))
		if not response['rows']:
			return None
		else:
			result = [r['doc'] for r in response['rows']]	
			return sorted(result, cmp=lambda x,y: cmp(x['author'], y['author']))
	
	def get_msc(author):
		# get zmath ids
		zmath_ids = [];
		for ids in author['coauthors'].values():
			zmath_ids += ids
		url = db_host + 'zmath' + '/_all_docs?include_docs=true'
		response = simplejson.load(urllib2.urlopen(url, simplejson.dumps({'keys': list(set(zmath_ids))})))
		if not response['rows']:
			return None
			
		# get msc tags
		msc_tags = []
		for r in response['rows']:
			if not 'doc' in r:
				continue
			line = r['doc'].get('cc', None)
			if not line:
				continue
			tags = line.split()
			# need to strip '*', replace '-XX' with '-++' and 'XX' with 'xx' 
			# in order to fetch the correct record in db: msc2010
			tags = [t.strip('*').replace('-XX', '-++').replace('XX', 'xx') for t in tags]
			msc_tags += tags
		msc_tags_count = {}
		for tag in msc_tags:
			if not tag in msc_tags_count:
				msc_tags_count[tag] = 0
			msc_tags_count[tag] += 1	
		url = db_host + 'msc2010' + '/_all_docs?include_docs=true'
		response = simplejson.load(urllib2.urlopen(url, simplejson.dumps({'keys': msc_tags_count.keys()})))
		if not response['rows']:
			return None
		
		# make return value
		docs = [r['doc'] for r in response['rows'] if 'doc' in r]
		result = [{'tag': d['_id'], 
				'tagText': d['tagText'], 
				'count': msc_tags_count[d['_id']]
				} for d in docs]
		return result
			
	def make_tree_node(author):
		result = {'id': author['author'],
				'name': author['author'],
				'adjacencies': []}
		for co_au in author['coauthors']:
			result['adjacencies'].append({"nodeTo": co_au,
											"data": {'count': len(author['coauthors'][co_au])},
											})
		data = {}
		for key in author:
			if not key in ['_id', '_rev', 'coauthors']:
				 data[key] = author[key]
		result['data'] = data
		return result
			
	# end of helper functions
	
	if request.GET.has_key('search'):
		search_term = request.GET.get('search')
		max_rows = request.GET.get('max_rows', 10)
		is_lucene = request.GET.get('is_lucene', False)
		if is_lucene:
			is_lucene = True
		result = search(search_term, max_rows, is_lucene)
		if result:
			return HttpResponse(simplejson.dumps(result), mimetype='application/json')
		else:
			return HttpResponse(simplejson.dumps({}), mimetype='application/json')
	

	key = request.GET.get('key', None)
	include_msc = request.GET.get('include_msc', False)
	if include_msc:
		include_msc = True
	# exclude_coauthors = request.GET.get('exclude_coauthors', False)
	# if exclude_coauthors:
	# 	exclude_coauthors = True
	exclude_coauthors = True #sets exclude_coauthors to reduce loading time
	
	author = None
	if not key:
		author = get_random_author()
	else:
		author = get_authors([key])
		author = author[0] if author else get_random_author()
	
	if not exclude_coauthors:
		coauthors = get_authors(author['coauthors'].keys())
	else:
		coauthors = [author]
	
	if include_msc:
		for au in coauthors:
			if au['author'] == author['author']:
				au['msc'] = get_msc(author)
	
	return HttpResponse(simplejson.dumps([make_tree_node(au) for au in coauthors]), mimetype='application/json')

def author_network(request): 
	return render_to_response('author_network.html')

def redirect(request,ext_url):
	return HttpResponseRedirect(ext_url)

def about(request):
	return render_to_response('about.html', {'title': 'About'})
	
def couchdb(request, url):
	result = 'null'
	if request.method == "GET":
		q = '?' + urllib.urlencode(request.GET) if request.GET else ''
		db_query = "http://localhost:5984/" + url + q
		result = urllib.urlopen(db_query).read()
	return HttpResponse(result)
